#pragma once

#include <iostream>
#include <string>

class ConcreteComponentA;
class ConcreteComponentB;

class Visitor
{
public:
    virtual void visitConcreteComponentA(const ConcreteComponentA *element) const = 0;
    virtual void visitConcreteComponentB(const ConcreteComponentB *element) const = 0;
};

class Component
{
public:
    virtual ~Component(){};

public:
    virtual void accept(Visitor *visitor) const = 0;
};

class ConcreteComponentA : public Component
{
public:
    void accept(Visitor *visitor) const override
    {
        visitor->visitConcreteComponentA(this);
    }
    std::string exclusiveMethodOfConcreteComponentA() const
    {
        return "A";
    }
};

class ConcreteComponentB : public Component
{
public:
    void accept(Visitor *visitor) const override
    {
        visitor->visitConcreteComponentB(this);
    }
    std::string specialMethodOfConcreteComponentB() const
    {
        return "B";
    }
};

class ConcreteVisitor1 : public Visitor
{
public:
    void visitConcreteComponentA(const ConcreteComponentA *element) const override
    {
        std::cout << element->exclusiveMethodOfConcreteComponentA() << " + ConcreteVisitor1\n";
    }

    void visitConcreteComponentB(const ConcreteComponentB *element) const override
    {
        std::cout << element->specialMethodOfConcreteComponentB() << " + ConcreteVisitor1\n";
    }
};

class ConcreteVisitor2 : public Visitor
{
public:
    void visitConcreteComponentA(const ConcreteComponentA *element) const override
    {
        std::cout << element->exclusiveMethodOfConcreteComponentA() << " + ConcreteVisitor2\n";
    }
    void visitConcreteComponentB(const ConcreteComponentB *element) const override
    {
        std::cout << element->specialMethodOfConcreteComponentB() << " + ConcreteVisitor2\n";
    }
};
